/*
************************************************************************************************************************
*                                                      uC/OS-III
*                                                 The Real-Time Kernel
*
*                                  (c) Copyright 2009-2014; Micrium, Inc.; Weston, FL
*                           All rights reserved.  Protected by international copyright laws.
*
*                                                 ISR QUEUE MANAGEMENT
*
* File    : OS_INT.C
* By      : JJL
* Version : V3.04.04
*
* LICENSING TERMS:
* ---------------
*           uC/OS-III is provided in source form for FREE short-term evaluation, for educational use or
*           for peaceful research.  If you plan or intend to use uC/OS-III in a commercial application/
*           product then, you need to contact Micrium to properly license uC/OS-III for its use in your
*           application/product.   We provide ALL the source code for your convenience and to help you
*           experience uC/OS-III.  The fact that the source is provided does NOT mean that you can use
*           it commercially without paying a licensing fee.
*
*           Knowledge of the source code may NOT be used to develop a similar product.
*
*           Please help us continue to provide the embedded community with the finest software available.
*           Your honesty is greatly appreciated.
*
*           You can find our product's user manual, API reference, release notes and
*           more information at https://doc.micrium.com.
*           You can contact us at www.micrium.com.
************************************************************************************************************************
*/

#define   MICRIUM_SOURCE
#include "os.h"

#ifdef VSC_INCLUDE_SOURCE_FILE_NAMES
const  CPU_CHAR  *os_int__c = "$Id: $";
#endif


#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
/*
************************************************************************************************************************
*                                                   POST TO ISR QUEUE
*
* Description: This function places contents of posts into an intermediate queue to help defer processing of interrupts
*              at the task level.
*
* Arguments  : type       is the type of kernel object the post is destined to:
*
*                             OS_OBJ_TYPE_SEM
*                             OS_OBJ_TYPE_Q
*                             OS_OBJ_TYPE_FLAG
*                             OS_OBJ_TYPE_TASK_MSG
*                             OS_OBJ_TYPE_TASK_SIGNAL
*
*              p_obj      is a pointer to the kernel object to post to.  This can be a pointer to a semaphore,
*              -----      a message queue or a task control clock.
*
*              p_void     is a pointer to a message that is being posted.  This is used when posting to a message
*                         queue or directly to a task.
*
*              msg_size   is the size of the message being posted
*
*              flags      if the post is done to an event flag group then this corresponds to the flags being
*                         posted
*
*              ts         is a timestamp as to when the post was done
*
*              opt        this corresponds to post options and applies to:
*
*                             OSFlagPost()
*                             OSSemPost()
*                             OSQPost()
*                             OSTaskQPost()
*
*              p_err      is a pointer to a variable that will contain an error code returned by this function.
*
*                             OS_ERR_NONE         if the post to the ISR queue was successful
*                             OS_ERR_INT_Q_FULL   if the ISR queue is full and cannot accepts any further posts.  This
*                                                 generally indicates that you are receiving interrupts faster than you
*                                                 can process them or, that you didn't make the ISR queue large enough.
*
* Returns    : none
*
* Note(s)    : none
************************************************************************************************************************
*/

void  OS_IntQPost(OS_OBJ_TYPE   type,
                  void         *p_obj,
                  void         *p_void,
                  OS_MSG_SIZE   msg_size,
                  OS_FLAGS      flags,
                  OS_OPT        opt,
                  CPU_TS        ts,
                  OS_ERR       *p_err)
{
    CPU_SR_ALLOC();
    CPU_CRITICAL_ENTER();

    if(OSIntQNbrEntries < OSCfg_IntQSize)                   /* Make sure we haven't already filled the ISR queue      */
    {
        OSIntQNbrEntries++;

        if(OSIntQNbrEntriesMax < OSIntQNbrEntries)
        {
            OSIntQNbrEntriesMax = OSIntQNbrEntries;
        }

        OSIntQInPtr->Type       = type;                     /* Save object type being posted                          */
        OSIntQInPtr->ObjPtr     = p_obj;                    /* Save pointer to object being posted                    */
        OSIntQInPtr->MsgPtr     = p_void;                   /* Save pointer to message if posting to a message queue  */
        OSIntQInPtr->MsgSize    = msg_size;                 /* Save the message size   if posting to a message queue  */
        OSIntQInPtr->Flags      = flags;                    /* Save the flags if posting to an event flag group       */
        OSIntQInPtr->Opt        = opt;                      /* Save post options                                      */
        OSIntQInPtr->TS         = ts;                       /* Save time stamp                                        */
        OSIntQInPtr             =  OSIntQInPtr->NextPtr;    /* Point to the next interrupt handler queue entry        */
        OSRdyList[0].NbrEntries = (OS_OBJ_QTY)1;            /* Make the interrupt handler task ready to run           */
        OSRdyList[0].HeadPtr    = &OSIntQTaskTCB;
        OSRdyList[0].TailPtr    = &OSIntQTaskTCB;
        OS_PrioInsert(0u);                                  /* Add task priority 0 in the priority table              */

        if(OSPrioCur != 0)                                  /* Chk if OSIntQTask is not running                       */
        {
            OSPrioSaved         = OSPrioCur;                /* Save current priority                                  */
        }

        *p_err                   = OS_ERR_NONE;
    }
    else
    {
        OSIntQOvfCtr++;                                     /* Count the number of ISR queue overflows                */
        *p_err                   = OS_ERR_INT_Q_FULL;
    }

    CPU_CRITICAL_EXIT();
}


/*
************************************************************************************************************************
*                                               RE-POST FROM ISR QUEUE
*
* Description: This function takes contents of posts from an intermediate queue to help defer processing of interrupts
*              at the task level.
*
* Arguments  : none
*
* Returns    : none
************************************************************************************************************************
*/

void  OS_IntQRePost(void)
{
#if OS_CFG_TMR_EN > 0u
    CPU_TS  ts;
#endif
    OS_ERR  err;

    switch(OSIntQOutPtr->Type)                              /* Re-post to task                                        */
    {
        case OS_OBJ_TYPE_FLAG:
#if OS_CFG_FLAG_EN > 0u
            (void)OS_FlagPost((OS_FLAG_GRP *) OSIntQOutPtr->ObjPtr,
                              (OS_FLAGS) OSIntQOutPtr->Flags,
                              (OS_OPT) OSIntQOutPtr->Opt,
                              (CPU_TS) OSIntQOutPtr->TS,
                              (OS_ERR      *)&err);
#endif
            break;

        case OS_OBJ_TYPE_Q:
#if OS_CFG_Q_EN > 0u
            OS_QPost((OS_Q      *) OSIntQOutPtr->ObjPtr,
                     (void      *) OSIntQOutPtr->MsgPtr,
                     (OS_MSG_SIZE) OSIntQOutPtr->MsgSize,
                     (OS_OPT) OSIntQOutPtr->Opt,
                     (CPU_TS) OSIntQOutPtr->TS,
                     (OS_ERR    *)&err);
#endif
            break;

        case OS_OBJ_TYPE_SEM:
#if OS_CFG_SEM_EN > 0u
            (void)OS_SemPost((OS_SEM *) OSIntQOutPtr->ObjPtr,
                             (OS_OPT) OSIntQOutPtr->Opt,
                             (CPU_TS) OSIntQOutPtr->TS,
                             (OS_ERR *)&err);
#endif
            break;

        case OS_OBJ_TYPE_TASK_MSG:
#if OS_CFG_TASK_Q_EN > 0u
            OS_TaskQPost((OS_TCB    *) OSIntQOutPtr->ObjPtr,
                         (void      *) OSIntQOutPtr->MsgPtr,
                         (OS_MSG_SIZE) OSIntQOutPtr->MsgSize,
                         (OS_OPT) OSIntQOutPtr->Opt,
                         (CPU_TS) OSIntQOutPtr->TS,
                         (OS_ERR    *)&err);
#endif
            break;

        case OS_OBJ_TYPE_TASK_RESUME:
#if OS_CFG_TASK_SUSPEND_EN > 0u
            (void)OS_TaskResume((OS_TCB *) OSIntQOutPtr->ObjPtr,
                                (OS_ERR *)&err);
#endif
            break;

        case OS_OBJ_TYPE_TASK_SIGNAL:
            (void)OS_TaskSemPost((OS_TCB *) OSIntQOutPtr->ObjPtr,
                                 (OS_OPT) OSIntQOutPtr->Opt,
                                 (CPU_TS) OSIntQOutPtr->TS,
                                 (OS_ERR *)&err);
            break;

        case OS_OBJ_TYPE_TASK_SUSPEND:
#if OS_CFG_TASK_SUSPEND_EN > 0u
            (void)OS_TaskSuspend((OS_TCB *) OSIntQOutPtr->ObjPtr,
                                 (OS_ERR *)&err);
#endif
            break;

        case OS_OBJ_TYPE_TICK:
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
            OS_SchedRoundRobin(&OSRdyList[OSPrioSaved]);
#endif
            (void)OS_TaskSemPost((OS_TCB *)&OSTickTaskTCB,                /* Signal tick task                        */
                                 (OS_OPT) OS_OPT_POST_NONE,
                                 (CPU_TS) OSIntQOutPtr->TS,
                                 (OS_ERR *)&err);
#if OS_CFG_TMR_EN > 0u
            OSTmrUpdateCtr--;

            if(OSTmrUpdateCtr == (OS_CTR)0u)
            {
                OSTmrUpdateCtr = OSTmrUpdateCnt;
                ts             = OS_TS_GET();                             /* Get timestamp                           */
                (void)OS_TaskSemPost((OS_TCB *)&OSTmrTaskTCB,             /* Signal timer task                       */
                                     (OS_OPT) OS_OPT_POST_NONE,
                                     (CPU_TS) ts,
                                     (OS_ERR *)&err);
            }

#endif
            break;

        default:
            break;
    }
}


/*
************************************************************************************************************************
*                                               INTERRUPT QUEUE MANAGEMENT TASK
*
* Description: This task is internal to uC/OS-III and is used to process the queue of deffered interrupts.
*
* Arguments  : p_arg     is a pointer to an optional argument that is passed during task creation.  For this function
*                        the argument is not used and will be a NULL pointer.
*
* Returns    : none
************************************************************************************************************************
*/

void  OS_IntQTask(void  *p_arg)
{
    CPU_BOOLEAN  done;
    CPU_TS       ts_start;
    CPU_TS       ts_end;
    CPU_SR_ALLOC();
    (void)&p_arg;                                           /* Not using 'p_arg', prevent compiler warning            */

    while(DEF_ON)
    {
        done = DEF_FALSE;

        while(done == DEF_FALSE)
        {
            CPU_CRITICAL_ENTER();

            if(OSIntQNbrEntries == (OS_OBJ_QTY)0u)
            {
                OSRdyList[0].NbrEntries = (OS_OBJ_QTY)0u;   /* Remove from ready list                                 */
                OSRdyList[0].HeadPtr    = (OS_TCB   *)0;
                OSRdyList[0].TailPtr    = (OS_TCB   *)0;
                OS_PrioRemove(0u);                          /* Remove from the priority table                         */
                CPU_CRITICAL_EXIT();
                OSSched();
                done = DEF_TRUE;                            /* No more entries in the queue, we are done              */
            }
            else
            {
                CPU_CRITICAL_EXIT();
                ts_start = OS_TS_GET();
                OS_IntQRePost();
                ts_end   = OS_TS_GET() - ts_start;          /* Measure execution time of tick task                    */

                if(OSIntQTaskTimeMax < ts_end)
                {
                    OSIntQTaskTimeMax = ts_end;
                }

                CPU_CRITICAL_ENTER();
                OSIntQOutPtr = OSIntQOutPtr->NextPtr;       /* Point to next item in the ISR queue                    */
                OSIntQNbrEntries--;
                CPU_CRITICAL_EXIT();
            }
        }
    }
}


/*
************************************************************************************************************************
*                                                 INITIALIZE THE ISR QUEUE
*
* Description: This function is called by OSInit() to initialize the ISR queue.
*
* Arguments  : p_err    is a pointer to a variable that will contain an error code returned by this function.
*
*                           OS_ERR_INT_Q             If you didn't provide an ISR queue in OS_CFG.C
*                           OS_ERR_INT_Q_SIZE        If you didn't specify a large enough ISR queue.
*                           OS_ERR_STK_INVALID       If you specified a NULL pointer for the task of the ISR task
*                                                    handler
*                           OS_ERR_STK_SIZE_INVALID  If you didn't specify a stack size greater than the minimum
*                                                    specified by OS_CFG_STK_SIZE_MIN
*                           OS_ERR_???               An error code returned by OSTaskCreate().
*
* Returns    : none
*
* Note(s)    : none
************************************************************************************************************************
*/

void  OS_IntQTaskInit(OS_ERR  *p_err)
{
    OS_INT_Q      *p_int_q;
    OS_INT_Q      *p_int_q_next;
    OS_OBJ_QTY     i;
    OSIntQOvfCtr = (OS_QTY)0u;                              /* Clear the ISR queue overflow counter                   */

    if(OSCfg_IntQBasePtr == (OS_INT_Q *)0)
    {
        *p_err = OS_ERR_INT_Q;
        return;
    }

    if(OSCfg_IntQSize < (OS_OBJ_QTY)2u)
    {
        *p_err = OS_ERR_INT_Q_SIZE;
        return;
    }

    OSIntQTaskTimeMax = (CPU_TS)0;
    p_int_q           = OSCfg_IntQBasePtr;                  /* Initialize the circular ISR queue                      */
    p_int_q_next      = p_int_q;
    p_int_q_next++;

    for(i = 0u; i < OSCfg_IntQSize; i++)
    {
        p_int_q->Type    =  OS_OBJ_TYPE_NONE;
        p_int_q->ObjPtr  = (void      *)0;
        p_int_q->MsgPtr  = (void      *)0;
        p_int_q->MsgSize = (OS_MSG_SIZE)0u;
        p_int_q->Flags   = (OS_FLAGS)0u;
        p_int_q->Opt     = (OS_OPT)0u;
        p_int_q->NextPtr = p_int_q_next;
        p_int_q++;
        p_int_q_next++;
    }

    p_int_q--;
    p_int_q_next        = OSCfg_IntQBasePtr;
    p_int_q->NextPtr    = p_int_q_next;
    OSIntQInPtr         = p_int_q_next;
    OSIntQOutPtr        = p_int_q_next;
    OSIntQNbrEntries    = (OS_OBJ_QTY)0u;
    OSIntQNbrEntriesMax = (OS_OBJ_QTY)0u;

    /* -------------- CREATE THE ISR QUEUE TASK ------------- */
    if(OSCfg_IntQTaskStkBasePtr == (CPU_STK *)0)
    {
        *p_err = OS_ERR_INT_Q_STK_INVALID;
        return;
    }

    if(OSCfg_IntQTaskStkSize < OSCfg_StkSizeMin)
    {
        *p_err = OS_ERR_INT_Q_STK_SIZE_INVALID;
        return;
    }

    OSTaskCreate((OS_TCB     *)&OSIntQTaskTCB,
                 (CPU_CHAR   *)((void *)"uC/OS-III ISR Queue Task"),
                 (OS_TASK_PTR)OS_IntQTask,
                 (void       *)0,
                 (OS_PRIO)0u,                               /* This task is ALWAYS at priority '0' (i.e. highest)     */
                 (CPU_STK    *)OSCfg_IntQTaskStkBasePtr,
                 (CPU_STK_SIZE)OSCfg_IntQTaskStkLimit,
                 (CPU_STK_SIZE)OSCfg_IntQTaskStkSize,
                 (OS_MSG_QTY)0u,
                 (OS_TICK)0u,
                 (void       *)0,
                 (OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)p_err);
}

#endif
